// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo, taekwon

#pragma once

#include <string>

#include "coin2/exchange/base/market/market_exchange_api.h"
#include "coin2/exchange/base/order/order_context_manager.h"
#include "coin2/exchange/base/order/update_manager.h"

namespace coin2::exchange::base::order {

class OrderQuerier {
 public:
  OrderQuerier(
      const OrderUpdateManager& upd_mgr,
      const OrderContextManager& order_manager,
      bool is_supporting_separate_pos)
      : upd_mgr_(upd_mgr),
        ocm_(order_manager),
        is_supporting_separate_pos_(is_supporting_separate_pos) {}

  const OrderUpdateManager& upd_mgr() const { return upd_mgr_; }

  const OrderContextManager& order_manager() const { return ocm_; }

  const MarketExchangeApi& mea() const { return upd_mgr_.mea(); }

  const AccountInfo& account_info() const { return upd_mgr_.account_info(); }

  bool has_position_info(const IProduct& product) const {
    if (product.exchange() == ExchangeType::Bitflyer &&
        StringEndsWith(product.relative_norm(), "IMMEDIATE")) {
      return account_info().HasBalance(product.base());
    } else if (product.market() == MarketType::Futures) {
      return account_info().HasPosition(product);
    } else if (product.market() == MarketType::Spot) {
      return account_info().HasBalance(product.base());
    } else {
      throw std::runtime_error("not reachable");
    }
  }

  double position(const IProduct& product) const {
    if (product.exchange() == ExchangeType::Bitflyer &&
        StringEndsWith(product.relative_norm(), "IMMEDIATE")) {
      return spot_position(product);
    } else if (product.market() == MarketType::Futures) {
      return futures_position(product);
    } else if (product.market() == MarketType::Options) {
      return futures_position(product);
    } else if (product.market() == MarketType::Spot) {
      return spot_position(product);
    } else {
      throw std::runtime_error("not reachable");
    }
  }

  double futures_position(const IProduct& product) const {
    CHECK_THROW(product.market() == MarketType::Futures || product.market() == MarketType::Options);
    return account_info().GetPosition(product).net_position();
  }

  double spot_position(const IProduct& product) const {
    if (product.exchange() != ExchangeType::Bitflyer ||
        !StringEndsWith(product.relative_norm(), "IMMEDIATE")) {
      CHECK_EQ_THROW(product.market(), MarketType::Spot)
          << product.exchange() << " " << product.relative_norm();
    }
    return account_info().GetBalance(product.base()).total();
  }

  double fill_position(const IProduct& product) const {
    if (product.market() == MarketType::Spot) {
      return upd_mgr_.GetFillPosition(product.base().currency());
    } else {
      return upd_mgr_.GetFillPosition(product.order_native_symbol());
    }
  }

  // only supported for china-style exchanges
  double open_long_avail_qty(const IProduct& product) {
    CHECK_EQ_THROW(product.market(), MarketType::Futures);
    CHECK_THROW(is_supporting_separate_pos_);
    return account_info().GetPosition(product).available_long_position();
  }
  double open_long_qty(const IProduct& product) {
    CHECK_EQ_THROW(product.market(), MarketType::Futures);
    CHECK_THROW(is_supporting_separate_pos_);
    return account_info().GetPosition(product).long_position();
  }
  double open_short_avail_qty(const IProduct& product) {
    CHECK_EQ_THROW(product.market(), MarketType::Futures);
    CHECK_THROW(is_supporting_separate_pos_);
    // short position = -3.0
    // short qty      = +3.0
    return -account_info().GetPosition(product).available_short_position();
  }
  double open_short_qty(const IProduct& product) {
    CHECK_EQ_THROW(product.market(), MarketType::Futures);
    CHECK_THROW(is_supporting_separate_pos_);
    // short position = -3.0
    // short qty      = +3.0
    return -account_info().GetPosition(product).short_position();
  }

  double GetBuyWorkingOrderQty(const IProduct& product, bool add_fill_unsynced = true) const {
    if (product.exchange() != ExchangeType::Bitflyer) {
      CHECK_EQ_THROW(product.market(), mea().market);
    }
    CHECK_EQ_THROW(product.exchange(), mea().exchange);

    double qty = 0;
    for (auto* order : order_manager().GetAllWorkingOrders()) {
      if (order->native_product() == product.order_native_symbol() &&
          order->direction() == OrderDirection::DIRECTION_BUY) {
        qty += order->working_qty();
      }
    }
    if (add_fill_unsynced) {
      qty += order_manager().GetFillNotSyncedToBuyPos(product.order_native_symbol());
      qty -= order_manager().GetFillNotSyncedToSellPos(product.order_native_symbol());
    }
    return qty;
  }
  double GetBuyCloseWorkingOrderQty(const IProduct& product) const {
    CHECK_EQ_THROW(product.market(), MarketType::Futures);
    CHECK_EQ_THROW(product.exchange(), mea().exchange);
    double qty = 0;
    for (auto* order : order_manager().GetAllWorkingOrders()) {
      if (order->native_product() == product.order_native_symbol() &&
          order->order_side() == OrderSide::BUY_CLOSE_ORDER) {
        qty += order->working_qty();
      }
    }
    return qty;
  }
  double GetSellWorkingOrderQty(const IProduct& product, bool add_fill_unsynced = true) const {
    if (product.exchange() != ExchangeType::Bitflyer) {
      CHECK_EQ_THROW(product.market(), mea().market);
    }
    CHECK_EQ_THROW(product.exchange(), mea().exchange);

    double qty = 0;
    for (auto* order : order_manager().GetAllWorkingOrders()) {
      if (order->native_product() == product.order_native_symbol() &&
          order->direction() == OrderDirection::DIRECTION_SELL) {
        qty += order->working_qty();
      }
    }
    if (add_fill_unsynced) {
      qty += order_manager().GetFillNotSyncedToSellPos(product.order_native_symbol());
      qty -= order_manager().GetFillNotSyncedToBuyPos(product.order_native_symbol());
    }
    return qty;
  }
  double GetSellCloseWorkingOrderQty(const IProduct& product) const {
    CHECK_EQ_THROW(product.market(), MarketType::Futures);
    CHECK_EQ_THROW(product.exchange(), mea().exchange);
    double qty = 0;
    for (auto* order : order_manager().GetAllWorkingOrders()) {
      if (order->native_product() == product.order_native_symbol() &&
          order->order_side() == OrderSide::SELL_CLOSE_ORDER) {
        qty += order->working_qty();
      }
    }
    return qty;
  }

  const OrderContext* FindByProcOrderId(int64_t proc_order_id) const {
    return ocm_.FindByProcOrderId(proc_order_id);
  }
  const auto& GetAllWorkingOrders() const { return ocm_.GetAllWorkingOrders(); }
  const auto& GetAllProductWorkingOrders(const std::string& native_symbol) const {
    return ocm_.GetAllProductWorkingOrders(native_symbol);
  }

 protected:
  const OrderUpdateManager& upd_mgr_;
  const OrderContextManager& ocm_;
  bool is_supporting_separate_pos_;
};

}  // namespace coin2::exchange::base::order
